package fr.pud.client.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import org.openstreetmap.gui.jmapviewer.Coordinate;
import org.openstreetmap.gui.jmapviewer.JMapViewer;
import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;

import fr.pud.client.controller.MapController;
import fr.pud.client.i18n.LanguagesConstants;
import fr.pud.client.view.mapstation.MapStation;

/**
 * Apr 9, 2012 - Projet_Ose.
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         JMapViewerExtended.java
 *         This class extends JMapViewer (a Java project which creates maps
 *         based on open street map) {@link http
 *         ://wiki.openstreetmap.org/wiki/JMapViewer}
 */
public class JMapViewerExtended extends JMapViewer {
    /**
     * The serial version uid
     */
    private static final long         serialVersionUID = -2593554861197444479L;
    /**
     * A singleton of the map created by JMapViewer and used to draw route
     */
    private static JMapViewerExtended singleton        = null;

    /**
     * @return the instance of the map
     */
    public static JMapViewerExtended getJMapViewer() {
        if (singleton == null) {
            singleton = new JMapViewerExtended();
        }
        return singleton;
    }

    /**
     * A list of route calculated by the software
     */
    private List<MapRoute>         mapRouteList;
    /**
     * If equals true, the routes will be drawn
     */
    private boolean                mapRouteVisible;
    /**
     * MapSuggestionAddresses is used to draw all the addresses suggestion found
     * by Nominatim
     */
    private MapSuggestionAddresses mapSuggestionAddresses = MapSuggestionAddresses
                                                                  .getMapSuggestionAddresses();
    /**
     * If equals true, the suggestions are visible
     */
    private boolean                mapSuggestionVisible;
    /**
     * A JPopupMenu which simulates the context menu
     */
    private JPopupMenu             contextMenu;
    /**
     * A JMenuItem which allows the user to add a MapAddress.
     */
    private JMenuItem              addMapAddress;

    /**
     * A constructor which creates a new map using a instance of JMapViewer.
     */
    private JMapViewerExtended() {
        super();
        this.mapRouteList = MapRoutes.getInstance().getRoutes();
        this.mapRouteVisible = true;
        this.mapSuggestionVisible = true;
        new MapController(this);
        this.prepareContextMenu();
    }

    /**
     * add a route to the list of routes
     * @param route
     *            , the route which will be add
     */
    public void addMapRoute(MapRoute route) {
        this.mapRouteList.add(route);
        this.repaint();
    }

    /**
     * Display on the map all the routes of the user results.
     * @param routes
     *            , The routes to display on the map.
     */
    public void addMapRoutes(MapRoutes routes) {
        for (MapRoute route : routes.getRoutes()) {
            this.addMapRoute(route);
        }
    }

    /**
     * @return the context menu
     */
    public JPopupMenu getContextMenu() {
        return this.contextMenu;
    }

    /**
     * @return the menu item of the context menu
     */
    public JMenuItem getContextMenuItem() {
        return this.addMapAddress;
    }

    public Point getLocation(int x, int y) {
        return this.getLocation(new Point(x, y));
    }

    /**
     * Return the location point on the map and not on the screen.
     * @param the
     *            point situated on the screen.
     */
    @Override
    public Point getLocation(Point p) {
        Point toReturn = new Point(-JMapViewerExtended.getJMapViewer()
                .getLocation().x + p.x, -JMapViewerExtended.getJMapViewer()
                .getLocation().y + p.y);
        return toReturn;
    }

    /**
     * @return the list of route calculated by the software
     */
    public List<MapRoute> getMapRouteList() {
        return this.mapRouteList;
    }

    /**
     * @return the list of the suggested addresses.
     */
    public MapSuggestionAddresses getMapSuggestionAddresses() {
        return this.mapSuggestionAddresses;
    }

    /**
     * @return true if the suggested addresses is visible.
     */
    public boolean isMapSuggestionVisible() {
        return this.mapSuggestionVisible;
    }

    /**
     * Move the map to the center
     * @param c
     */
    public void MoveMapToCenter(Coordinate c) {
        super.setDisplayPositionByLatLon(c.getLat(), c.getLon(),
                JMapViewerExtended.getJMapViewer().getZoom());
    }

    /**
     * Paint all the components on the graph
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (this.mapRouteList != null) {
            for (MapRoute route : this.mapRouteList) {
                this.paintRoute(g, route);
            }
        }
        if (this.mapSuggestionAddresses != null) {
            this.paintSuggestions(g, this.mapSuggestionAddresses);
        }
        MapQueries.getInstance().paint(g);
    }

    /**
     * Paint a single marker.
     */
    @Override
    protected void paintMarker(Graphics g, MapMarker marker) {
        Point p = this.getMapPosition(marker.getLat(), marker.getLon(), true);
        marker.paint(g, p);
    }

    /**
     * prepare the Map point of the route with a list of coordinate and paint
     * the route
     * @param g
     *            , the graphics where the route is painted
     * @param aRoute
     *            , the route we want to paint
     */
    protected void paintRoute(Graphics g, MapRoute aRoute) {
        ArrayList<Point> routePoint = new ArrayList<Point>();
        for (int indexTrajet = 0; indexTrajet < aRoute.getSize(); indexTrajet++) {
            if (aRoute.getMapStation(indexTrajet) != null) {
                Point p = this.getMapPosition(aRoute.getMapStation(indexTrajet)
                        .getLat(), aRoute.getMapStation(indexTrajet).getLon());
                routePoint.add(p);
                aRoute.getMapStation(indexTrajet).paintRoutesMarkers(g,
                        routePoint.get(indexTrajet), aRoute.isVisible());
            }
        }
        if (this.mapRouteVisible && routePoint != null) {
            aRoute.paint(g, routePoint);
        }
    }

    /**
     * Paint addresses which are the result of a user queries with Nominatim.
     * Those one are not add to the PUDStation list.
     * @param g
     *            , the graphics where the addresses are displayed
     * @param suggestionAddresses
     *            , the list of addresses to paint.
     */
    protected void paintSuggestions(Graphics g,
            MapSuggestionAddresses suggestionAddresses) {
        for (MapStation station : suggestionAddresses.getSuggestions()) {
            station.paintSugestionsMarkers(g,
                    this.getMapPosition(station.getCoordonneesAddress()));
        }
    }

    /**
     * Prepare the context menu used to add some data on the map
     */
    private void prepareContextMenu() {
        this.contextMenu = new JPopupMenu();
        this.addMapAddress = new JMenuItem(
                LanguagesConstants.LANGUAGE
                        .getString(LanguagesConstants.ADD_ADDRESS));
        this.contextMenu.add(this.addMapAddress);
        this.contextMenu.setBorder(BorderFactory.createLineBorder(Color.black,
                1));
        this.contextMenu.setVisible(false);
        this.add(this.contextMenu);
    }

    /**
     * This method change the visibility of the marker drawn on the map.
     * If true, the markers are visible.
     */
    @Override
    public void setMapMarkerVisible(boolean isVisible) {
        super.mapMarkersVisible = isVisible;
        this.repaint();
    }

    /**
     * @param maprouteVisible
     *            the maprouteVisible to set
     */
    public void setMapRouteVisible(boolean maprouteVisible) {
        this.mapRouteVisible = maprouteVisible;
        this.repaint();
    }

    /**
     * Make visible or not the suggested addresses.
     * @param mapSuggestionVisible
     *            , if true, the addresses are visible.
     */
    public void setMapSuggestionVisible(boolean mapSuggestionVisible) {
        this.mapSuggestionVisible = mapSuggestionVisible;
        this.repaint();
    }
}
